package pwgen

import (
	"crypto/rand"
	"math/big"
	"strings"
)

/*
// 生成默认密码（12位，包含所有字符类型）
password, err := pwgen.Generate()

// 生成16位密码
password, err := pwgen.Generate(pwgen.WithLength(16))

// 生成不包含特殊字符的密码
password, err := pwgen.Generate(pwgen.WithoutSpecial())

// 生成只包含数字的8位密码
password, err := pwgen.Generate(pwgen.OnlyDigits(), pwgen.WithLength(8))

// 组合多个选项
password, err := pwgen.Generate(
    pwgen.WithLength(20),
    pwgen.WithoutSpecial(),
    pwgen.WithoutDigits(),
)

// 忽略单个特殊字符
password, err := pwgen.Generate(pwgen.IgnoreSpecialChars("@"))

// 忽略多个特殊字符
password, err := pwgen.Generate(pwgen.IgnoreSpecialChars("@", "#", "$"))

// 忽略特定字符序列
password, err := pwgen.Generate(pwgen.IgnoreSpecialChars("@@"))

// 组合使用
password, err := pwgen.Generate(
    pwgen.WithLength(16),
    pwgen.IgnoreSpecialChars("@", "##", "$%^")
)

// 使用自定义特殊字符集
password, err := pwgen.Generate(
    pwgen.WithLength(16),
    pwgen.WithSpecialChars("!@#$%")
)

// 使用自定义特殊字符集同时忽略特定字符
password, err := pwgen.Generate(
    pwgen.WithLength(16),
    pwgen.WithSpecialChars("!@#$%^&*()"),
    pwgen.IgnoreSpecialChars("@", "#")
)

// 设置空特殊字符集（等同于WithoutSpecial）
password, err := pwgen.Generate(
    pwgen.WithLength(16),
    pwgen.WithSpecialChars("")
)

*/

// Character sets
const (
	UppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	LowercaseChars = "abcdefghijklmnopqrstuvwxyz"
	SpecialChars   = "!@#$%^&*()-_=+[]{}|;:,.<>?/"
	DigitChars     = "0123456789"
)

// config holds the internal configuration for password generation
type config struct {
	length             int
	includeUppercase   bool
	includeLowercase   bool
	includeSpecial     bool
	includeDigits      bool
	ignoreSpecialChars []string //忽略的SpecialChars字符序列
	customSpecialChars string   //自定义的特殊字符集
}

// defaultConfig returns the default configuration
func defaultConfig() *config {
	return &config{
		length:           12,
		includeUppercase: true,
		includeLowercase: true,
		includeSpecial:   true,
		includeDigits:    true,
	}
}

// Option defines a function that can modify the password generation config
type Option func(*config)

// WithLength sets the password length
func WithLength(length int) Option {
	return func(c *config) {
		if length > 0 {
			c.length = length
		}
	}
}

// WithoutUppercase excludes uppercase letters from the password
func WithoutUppercase() Option {
	return func(c *config) {
		c.includeUppercase = false
	}
}

// WithoutLowercase excludes lowercase letters from the password
func WithoutLowercase() Option {
	return func(c *config) {
		c.includeLowercase = false
	}
}

// WithoutSpecial excludes special characters from the password
func WithoutSpecial() Option {
	return func(c *config) {
		c.includeSpecial = false
	}
}

// WithoutDigits excludes digits from the password
func WithoutDigits() Option {
	return func(c *config) {
		c.includeDigits = false
	}
}

// OnlyUppercase configures the password to only include uppercase letters
func OnlyUppercase() Option {
	return func(c *config) {
		c.includeUppercase = true
		c.includeLowercase = false
		c.includeSpecial = false
		c.includeDigits = false
	}
}

// OnlyLowercase configures the password to only include lowercase letters
func OnlyLowercase() Option {
	return func(c *config) {
		c.includeUppercase = false
		c.includeLowercase = true
		c.includeSpecial = false
		c.includeDigits = false
	}
}

// OnlyDigits configures the password to only include digits
func OnlyDigits() Option {
	return func(c *config) {
		c.includeUppercase = false
		c.includeLowercase = false
		c.includeSpecial = false
		c.includeDigits = true
	}
}

// OnlySpecial configures the password to only include special characters
func OnlySpecial() Option {
	return func(c *config) {
		c.includeUppercase = false
		c.includeLowercase = false
		c.includeSpecial = true
		c.includeDigits = false
	}
}

// IgnoreSpecialChars specifies exact character sequences to exclude from the generated password
func IgnoreSpecialChars(chars ...string) Option {
	return func(c *config) {
		c.ignoreSpecialChars = chars
	}
}

// WithSpecialChars allows specifying a custom set of special characters to use
func WithSpecialChars(chars string) Option {
	return func(c *config) {
		c.customSpecialChars = chars
		// 如果指定了空字符串作为特殊字符集，则禁用特殊字符
		if chars == "" {
			c.includeSpecial = false
		}
	}
}

// Generate creates a password with the specified options
func Generate(options ...Option) (string, error) {
	// Start with default config
	cfg := defaultConfig()

	// Apply all options
	for _, opt := range options {
		opt(cfg)
	}

	// Build character set based on options
	var charset string
	var requiredChars []byte

	if cfg.includeUppercase {
		charset += UppercaseChars
		char, err := getRandomChar(UppercaseChars)
		if err != nil {
			return "", err
		}
		requiredChars = append(requiredChars, char)
	}

	if cfg.includeLowercase {
		charset += LowercaseChars
		char, err := getRandomChar(LowercaseChars)
		if err != nil {
			return "", err
		}
		requiredChars = append(requiredChars, char)
	}

	if cfg.includeSpecial {
		specialSet := SpecialChars

		// 如果指定了自定义特殊字符集，则使用它
		if cfg.customSpecialChars != "" {
			specialSet = cfg.customSpecialChars
		}

		// 从特殊字符集中移除需要忽略的字符序列
		if len(cfg.ignoreSpecialChars) > 0 {
			// 处理单字符忽略情况
			charSlice := []rune(specialSet)
			var filteredChars []rune

			for _, char := range charSlice {
				shouldInclude := true
				charStr := string(char)

				// 检查当前字符是否在任何需要忽略的序列中
				for _, seq := range cfg.ignoreSpecialChars {
					// 只处理单字符情况，多字符序列在生成密码后处理
					if seq == charStr && len(seq) == 1 {
						shouldInclude = false
						break
					}
				}

				if shouldInclude {
					filteredChars = append(filteredChars, char)
				}
			}

			specialSet = string(filteredChars)
		}

		charset += specialSet
		if specialSet != "" {
			char, err := getRandomChar(specialSet)
			if err != nil {
				return "", err
			}
			requiredChars = append(requiredChars, char)
		}
	}

	if cfg.includeDigits {
		charset += DigitChars
		char, err := getRandomChar(DigitChars)
		if err != nil {
			return "", err
		}
		requiredChars = append(requiredChars, char)
	}

	// If no character sets were selected, use a default
	if charset == "" {
		charset = LowercaseChars
		char, err := getRandomChar(LowercaseChars)
		if err != nil {
			return "", err
		}
		requiredChars = append(requiredChars, char)
	}

	// Ensure minimum length to include all required characters
	if cfg.length < len(requiredChars) {
		cfg.length = len(requiredChars)
	}

	// Create password with required characters first
	passwordBytes := requiredChars

	// Fill the rest with random characters
	for i := len(requiredChars); i < cfg.length; i++ {
		char, err := getRandomChar(charset)
		if err != nil {
			return "", err
		}
		passwordBytes = append(passwordBytes, char)
	}

	// Shuffle the password
	if err := shuffleBytes(passwordBytes); err != nil {
		return "", err
	}

	password := string(passwordBytes)

	// 处理多字符序列忽略
	if len(cfg.ignoreSpecialChars) > 0 {
		// 找出所有多字符序列
		var multiCharSequences []string
		for _, seq := range cfg.ignoreSpecialChars {
			if len(seq) > 1 {
				multiCharSequences = append(multiCharSequences, seq)
			}
		}

		// 如果有多字符序列需要处理
		if len(multiCharSequences) > 0 {
			// 替换所有多字符序列
			for _, seq := range multiCharSequences {
				for strings.Contains(password, seq) {
					// 找到多字符序列的位置
					pos := strings.Index(password, seq)
					if pos != -1 {
						// 需要替换序列中的每个字符
						// 为了保持密码长度和复杂度，我们用随机字符替换
						replacement := make([]byte, len(seq))
						for i := 0; i < len(seq); i++ {
							// 从与原密码相同的字符集生成替代字符
							newChar, err := getRandomChar(charset)
							if err != nil {
								return "", err
							}
							replacement[i] = newChar
						}

						// 构建新密码
						passwordBytes = []byte(password)
						copy(passwordBytes[pos:pos+len(seq)], replacement)
						password = string(passwordBytes)
					}
				}
			}
		}
	}

	return password, nil
}

// getRandomChar returns a random character from the given character set
func getRandomChar(charset string) (byte, error) {
	if charset == "" {
		return 0, nil
	}

	max := big.NewInt(int64(len(charset)))
	n, err := rand.Int(rand.Reader, max)
	if err != nil {
		return 0, err
	}

	return charset[n.Int64()], nil
}

// shuffleBytes randomizes the order of bytes in a slice
func shuffleBytes(slice []byte) error {
	for i := range slice {
		// Generate a random index j
		j, err := rand.Int(rand.Reader, big.NewInt(int64(i+1)))
		if err != nil {
			return err
		}
		// Swap slice[i] and slice[j]
		slice[i], slice[j.Int64()] = slice[j.Int64()], slice[i]
	}
	return nil
}
